#include "Tilemap.h"
#include "Utility.h"
#include <iostream>

TileMap::TileMap(int tileSz, sf::Vector2f position, sf::Vector2f dimensions, sf::Texture* texture){

    tileSize = sf::Vector2f(tileSz,tileSz);
    mapWidth = dimensions.x;    //width of the map, in tiles
    mapHeight = dimensions.y;   //height of the map, in tiles
    mapPosition = position;     //x,y location of the top left corner of the map, in world coords
    //std::cout << "MapWidth: " << mapWidth << " MapHeight: " << mapHeight << std::endl;
    textureTileSize.x = tileSize.x;    //to get a texturesize of 64px
    textureTileSize.y = tileSize.y;
    m_tileset = texture;

    // resize the vertex array to fit the level size
    fogLayer.setPrimitiveType(sf::Quads);
    fogLayer.resize(mapWidth * mapHeight * 4);
    baseLayer.setPrimitiveType(sf::Quads);
    baseLayer.resize(mapWidth * mapHeight * 4);
    middleLayer.setPrimitiveType(sf::Quads);
    middleLayer.resize(mapWidth * mapHeight * 4);

    //set the size and position of the walk rectangle

    int block_offset = 0;   //each map needs a 6 block border to prevent player from walking to edge
    walkBounds.setSize(sf::Vector2f((mapWidth - 2 * block_offset) * tileSize.x  , (mapHeight - 2 * block_offset) * tileSize.x ));
    walkBounds.setPosition(sf::Vector2f(block_offset * tileSize.x , block_offset * tileSize.y));
    walkBounds.setFillColor(sf::Color::Transparent);
    walkBounds.setOutlineThickness(2);
    walkBounds.setOutlineColor(sf::Color::Red);

    resetTileMap(0);    //base layer
    resetTileMap(1);    //middle layer
    resetTileMap(2);    //Fog Layer

    //Make a lake
    //makeDiamond(2550, 48, sf::Vector2i(20,10));
    //makeDiamond(2460, 48, sf::Vector2i(5,10));
    //makeDiamond(2940, 48, sf::Vector2i(2,15));

    //smooth
    /*
    int smoothLayer = 1;
    int checkType = 16;
    for(int i = 0; i < tiles.size(); i++){
        if(getTileType(i,smoothLayer) != 0) smoothEdges(i,checkType,smoothLayer,smoothLayer);
    }
    */


    //Add diamond shaped features at random locations on the map of various sizes
    /*
    int numFeatures = 50;
    int tempType;
    for (int i = 0; i < numFeatures; i++){
        tempType = Utility::random(12,20);
        makeDiamond(Utility::random(2500,tiles.size()-2500), tempType,
                    sf::Vector2i(6,4) );    //will crash if diamond does not fit in map
    }
    */
}

void TileMap::resetTileMap(int layer){

    //int tileTestType = Utility::random(12,23);   //temp setting of all tiles to tileTestType

    int tempTileTexture;

    // populate the vertex array, with one quad per tile i = y , j = x
    for (unsigned int i = 0; i < mapHeight; ++i)
        for (unsigned int j = 0; j < mapWidth; ++j)
        {
            // get a pointer to the current tile's quad
            sf::Vertex* quad;

            //add the tile to the tile list
            sf::FloatRect tempRect = sf::FloatRect(mapPosition.x + (j * tileSize.x),
                                                    mapPosition.y + (i* tileSize.y),
                                                   tileSize.x, tileSize.y);
            //create the tile
            tiles.push_back(new tile(tempRect));

            //set the quad ID and tempTileTexture for the layer
            if(layer ==0){  //base layer
                quad = &baseLayer[(j + i * mapWidth) * 4];
                tempTileTexture = 16;

            }else if(layer ==1){  //middle layer
                quad = &middleLayer[(j + i * mapWidth) * 4];
                tempTileTexture = 16;
                //tempTileTexture = Utility::random(16,48);

            }else{  //fog layer
                quad = &fogLayer[(j + i * mapWidth) * 4];
                tempTileTexture = 1;
            }

            //Set the texture for the quad of defined layer
            int rotation = Utility::random(0,3);    //randomly rotate the texture
            setLayerTexture(j + i * mapWidth, tempTileTexture , layer,rotation);

            // define its 4 corners
            quad[0].position = sf::Vector2f(mapPosition.x + j * tileSize.x, mapPosition.y + i * tileSize.y);
            quad[1].position = sf::Vector2f(mapPosition.x + (j + 1) * tileSize.x, mapPosition.y +i * tileSize.y);
            quad[2].position = sf::Vector2f(mapPosition.x + (j + 1) * tileSize.x, mapPosition.y +(i + 1) * tileSize.y);
            quad[3].position = sf::Vector2f(mapPosition.x + j * tileSize.x, mapPosition.y +(i + 1) * tileSize.y);
        }
}


void TileMap::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
    // apply the transform
    states.transform *= getTransform();

    // apply the tileset texture
    states.texture = m_tileset;

    // draw the vertex arrays
    target.draw(baseLayer, states);
    target.draw(middleLayer, states);
    target.draw(fogLayer, states);
}

//Returns the center point of the map in world coordinates
sf::Vector2f TileMap::getWorldMapCenter(){
    return sf::Vector2f(mapPosition.x + ((mapWidth * tileSize.x) / 2), mapPosition.y + ((mapHeight * tileSize.y) / 2));
}

//Change the type, texture, and texture orientation of a tile layer
void TileMap::setLayerTexture(unsigned int tempTileNumber, int tempTileType, int layer, int rotation){

            // get a pointer to the current tile's quad (i.e. the square inside the vector array
            sf::Vertex* quad;
            int quadNumber = tempTileNumber * 4;
            if(layer == 0){
                quad = &baseLayer[quadNumber];
            }else if (layer == 1) {
                quad = &middleLayer[quadNumber];
            }else{
                quad = &fogLayer[quadNumber];
            }

            //Display the texture at a random direction
            std::vector<int> order(4);
            switch (rotation){
                case 0:
                     order = {0,1,2,3}; //right side up
                     break;
                case 1:
                    order = {2,3,0,1};  //upside down and flipped
                    break;
                case 2:
                    order = {1,2,3,0};  //rotate clockwise 90
                    break;
                case 3:
                    order = {3,0,1,2};  //rotate counterclockwise 90
                    break;
            }

            // find its position in the tileset texture
            int column = tempTileType % (m_tileset->getSize().x / textureTileSize.x );
            int row = tempTileType / (m_tileset->getSize().x / textureTileSize.x );

            // define its 4 texture coordinates
            quad[order[0]].texCoords = sf::Vector2f(column * textureTileSize.x , row * textureTileSize.y);
            quad[order[1]].texCoords = sf::Vector2f((column + 1) * textureTileSize.x, row * textureTileSize.y);
            quad[order[2]].texCoords = sf::Vector2f((column + 1) * textureTileSize.x, (row + 1) * textureTileSize.y);
            quad[order[3]].texCoords = sf::Vector2f(column * textureTileSize.x, (row + 1) * textureTileSize.y);

            //define the type for the layer
            tiles[tempTileNumber]->setTileType(layer, tempTileType);
}

//Returns the dimensions of the world in pixels
sf::Vector2f TileMap::getWorldDimensions(){
    return sf::Vector2f(mapWidth * tileSize.x, mapHeight * tileSize.y);
}

//Returns the tile # of the point provided
unsigned int TileMap::getTileNumber(sf::Vector2f pos){

    unsigned int i = (pos.x - mapPosition.x) / tileSize.x ;
    unsigned int j = (pos.y - mapPosition.y) / tileSize.y;
    unsigned int tile = i + j * mapWidth;

    //std::cout << "getTileNumber() Returns: " << tile << std::endl;

    if(tile <0){
        return 0;
    } if(tile > tiles.size()){
        return tiles.size();
    }else{
        return tile;
    }

}

//Returns the width of the map, in tiles
int TileMap::getMapTileWidth(){
    return mapWidth;
}

//Returns the number of total tiles in the map.
unsigned int TileMap::getTotalTiles(){
    return tiles.size();
}

//Returns the Global Bounds of the Tile Rect
sf::FloatRect TileMap::getTileRect(unsigned int tileNum){
    return tiles[tileNum]->tileRect;
}

//Returns the x,y coordinates of the map (upper left corner)
sf::Vector2f TileMap::getMapPosition(){
    return mapPosition;
}

//creates a diamond shape of tiles at the specifed tile center
void TileMap::makeDiamond(int tileCenter, int type, sf::Vector2i tSize){

    int layer = 0;  //Temp layer

    for(int i = -tSize.x ; i <= tSize.x; i++){
        for(int j = std::abs(i)-tSize.y; j <= (std::abs(i)-tSize.y) *-1; j++){
            setLayerTexture(tileCenter + i * mapWidth - j , type,layer,0);
            setLayerTexture(tileCenter + i * mapWidth - j , 0, layer+1 ,0);
        }
    }
}

//creates a diamond shape of tiles at the specifed tile center
void TileMap::makeSquare(int tileStart, int type, sf::Vector2i tSize){

    for(int i = 0 ; i <= tSize.x; i++){
        for(int j = 0; j <= tSize.y; j++){
            setLayerTexture(tileStart + j * mapWidth + i , type,0,0);
        }
    }
}

//returns the type number of the selected tilenumber for the map
int TileMap::getTileType(unsigned int tileNumber, int layer){
    return tiles[tileNumber]->getTileType(layer);
}

//update the texture of the tile based on its relationship to it's neighbors
//Input type == textureID of The 'full' texture, the first in the series of 5
void TileMap::smoothEdges(int tileNumber, int inputType, int sourceLayer, int targetLayer){


    //create a vector of 4 int
    std::vector<int> neighbors;

    //Set the textureID and required rotation for each number option
    int textureID = 0;
    int rotation = 0;

    //for each tile, check the up, down, right, and left neighbors, if they exist
    //if the neighbor tile equals the tileType, set that vector index to 1

    //check from +1 and -1 row of the map, otherwise crash
    if(tileNumber > mapWidth && tileNumber < (tiles.size()-mapWidth)){
        if(getTileType(tileNumber - mapWidth,sourceLayer) != 0 ){
            //std::cout << "Should be 1" << std::endl;
            neighbors.push_back(1);
        }else neighbors.push_back(0);

        //tile left
        if(getTileType(tileNumber -1,sourceLayer) != 0 ){
            neighbors.push_back(1);
        }else neighbors.push_back(0);

        //tile down
        if(getTileType(tileNumber + mapWidth,sourceLayer) != 0  ){
            neighbors.push_back(1);
        }else neighbors.push_back(0);

        //tile right
        if(getTileType(tileNumber + 1,sourceLayer) != 0  ){
            neighbors.push_back(1);
        }else neighbors.push_back(0);

        //take the vector and convert the values to a single number [0] = 1000, [1] = 100, [2] = 10, [3] = 1
        int composite = neighbors[0] * 1000 + neighbors[1] * 100 + neighbors[2] * 10 + neighbors[3];

        //if(composite == 1001) std::cout << "Composite: " << composite << std::endl;

        switch (composite){
            case 1000:  //3 wedge, rotate 0
                textureID = 4;
                rotation = 0;
                break;
            case 100: // 3 wedge, rotate 90 CCW
                textureID = 4;
                rotation = 3;
                break;
            case 10:    //3 wedge, rotate 180
                textureID = 4;
                rotation = 1;
                break;
            case 1:     //3 wedge, rotate 90 CW
                textureID = 4;
                rotation = 2;
                break;
            case 1100: //split, rotate 90 CCW
                textureID = 2;
                rotation = 3;
                break;
            case 11: //split, rotate 90 CW
                textureID = 2;
                rotation = 2;
                break;
            case 1001:	//split, no rotate
                textureID = 2;
                rotation = 0;
                break;
            case 1010: //hourglass, no rotation
                textureID = 3;
                rotation = 0;
                break;
            case 110:  // split, rotate 180
                textureID = 2;
                rotation = 1;
                break;
            case 101:	//hourglass, 90 rotatation
                textureID = 3;
                rotation = 2;
                break;
            case 1011:	//open up, rotate 90 CCW
                textureID = 1;
                rotation = 3;
                break;
            case 111: 	//open up
                textureID = 1;
                rotation = 0;
                break;
            case 1101:	//open up, rotate 180
                textureID = 1;
                rotation = 1;
                break;
            case 1110:	// open up, rotate 90 CW
                textureID = 1;
                rotation = 2;
                break;
            case 1111:	//full block
                textureID = 0;
                rotation = 0;
                break;
            case 0:	//empty
                textureID = 5;
                rotation = 0;
                break;
            default:
                break;
        }
    }


    //change the tile's texture
    if(textureID == 5){
        //setLayerTexture(tileNumber,0,sourceLayer,0);
    }else{
        setLayerTexture(tileNumber,inputType + textureID,sourceLayer,rotation);
    }

}

void TileMap::setTileTransparency(unsigned int layer, unsigned int tempTileNumber, unsigned int trans){

    sf::Vertex* quad;
    int quadNumber = tempTileNumber * 4;
    if(layer == 0){
        quad = &baseLayer[quadNumber];
    }else if (layer == 1) {
        quad = &middleLayer[quadNumber];
    }else{
        quad = &fogLayer[quadNumber];
    }

    //check limit
    if(trans > 255) trans = 255;

    //change the trans of the defined quad
    for(int i = 0; i < 4; i++) quad[i].color = sf::Color(0,0,0,trans);
}

//Set the tile at the defined location to explored
void TileMap::exploreTile(sf::Vector2f position){
    tiles[getTileNumber(position)]->toggleExplored();
}

//Returns whether the tile at a given position is explored
bool TileMap::isExplored(sf::Vector2f position){
    if(tiles[getTileNumber(position)]->isExplored()) return true;
    return false;
}

//returns the position of the tile center
sf::Vector2f TileMap::getTileCenter(unsigned int tileNumber){

sf::FloatRect r = getTileRect(tileNumber);

return sf::Vector2f(r.left + r.width/2, r.top + r.height/2);

}

//returns the walk bounds rectangle
sf::RectangleShape TileMap::getWalkBounds(){
    return walkBounds;
}



